/*
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.googlecode.j4sc.submission.audio;

import java.io.BufferedInputStream;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;

import javax.sound.sampled.AudioFileFormat;
import javax.sound.sampled.AudioFormat;
import javax.sound.sampled.AudioSystem;
import javax.sound.sampled.UnsupportedAudioFileException;
import javax.sound.sampled.AudioFileFormat.Type;

import org.apache.commons.codec.binary.Base64;
import org.apache.commons.io.IOUtils;

/**
 * The Audio to be sent in the submission to the service.
 * <p>
 * On construction, the audio is checked to ensure it is G.711 a-law or u-law
 * encoded, 8000 MHz sample rate and mono channel
 * <p>
 * The audio length is also checked to ensure it is 30 seconds or less
 * <p>
 * 
 * @author Raj Patel, Dariusz Lorenc
 */
public class Audio {

    private static final String TOO_LONG_MESSAGE = "The audio file must be no more than 30 seconds in length";
    private static final String INVALID_AUDIO_MESSAGE = "The audio file must be encoded as a "
            + "G.711 A-Law 8 kHz mono WAVE file or a G.711 u-Law 8 kHz mono WAVE file";
    private static final int MAX_AUDIO_LENGTH = 30;
    private static final int SAMPLE_RATE = 8000;
    private static final int MONO = 1;

    private byte[] audioBytes;

    /**
     * Construct a new instance
     * 
     * @param audioBytes
     *            the audio bytes
     * @throws IOException
     * @throws InvalidAudioException
     *             if the audio is not encoded correctly or invalid length
     */
    public Audio(byte[] audioBytes) throws IOException, InvalidAudioException {
        this(new ByteArrayInputStream(audioBytes));
    }

    /**
     * Construct a new instance
     * 
     * @param file
     *            the audio file
     * @throws IOException
     * @throws InvalidAudioException
     *             if the audio is not encoded correctly or invalid length
     */
    public Audio(File file) throws IOException, InvalidAudioException {
        this(new FileInputStream(file));
    }

    /**
     * Construct a new instance
     * 
     * @param stream
     *            the audio stream
     * @throws IOException
     * @throws InvalidAudioException
     *             if the audio is not encoded correctly or invalid length
     */
    public Audio(InputStream stream) throws IOException, InvalidAudioException {
        BufferedInputStream bufferedInputStream = new BufferedInputStream(stream);
        AudioFileFormat fileFormat = getAudioFileFormat(bufferedInputStream);

        if (!isValidAudio(fileFormat)) {
            throw new InvalidAudioException(INVALID_AUDIO_MESSAGE);
        }
        if (!isValidLength(fileFormat)) {
            throw new InvalidAudioException(TOO_LONG_MESSAGE);
        }

        audioBytes = IOUtils.toByteArray(bufferedInputStream);
    }

    /**
     * The audio as a BASE 64 encoded string chunked to 76 character blocks
     * 
     * @return the base 64 string
     */
    public String toBase64() {
        return new String(Base64.encodeBase64(audioBytes, true));
    }

    private boolean isValidAudio(AudioFileFormat fileFormat) throws IOException, InvalidAudioException {
        AudioFormat format = fileFormat.getFormat();
        if (isWave(fileFormat.getType()) && isAlawOrUlaw(format.getEncoding()) && isMonoChannel(format)
                && isValidSampleRate(format)) {
            return true;
        } else {
            return false;
        }
    }

    private AudioFileFormat getAudioFileFormat(BufferedInputStream stream) throws IOException, InvalidAudioException {
        AudioFileFormat fileFormat;
        try {
            fileFormat = AudioSystem.getAudioFileFormat(stream);
        } catch (UnsupportedAudioFileException e) {
            throw new InvalidAudioException(INVALID_AUDIO_MESSAGE, e);
        }
        return fileFormat;
    }

    private boolean isMonoChannel(AudioFormat format) {
        return format.getChannels() == MONO;
    }

    private boolean isAlawOrUlaw(AudioFormat.Encoding encoding) {
        return (encoding == AudioFormat.Encoding.ALAW || encoding == AudioFormat.Encoding.ULAW);
    }

    private boolean isValidSampleRate(AudioFormat format) {
        return format.getSampleRate() == SAMPLE_RATE;
    }

    private boolean isValidLength(AudioFileFormat fileFormat) {
        float audioLength = fileFormat.getFrameLength() / fileFormat.getFormat().getSampleRate();
        return audioLength <= MAX_AUDIO_LENGTH;
    }

    private boolean isWave(Type type) {
        return type == Type.WAVE;
    }
}
